package p23_类型转换与绑定;

import cn.hutool.core.date.DateUtil;
import lombok.Data;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.Formatter;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;

import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * DataBinder是高层接口的主要实现类，将数据源的数据绑定到目标对象的属性上，如果数据源的数据类型与对象的属性类型不一致，则需要类型转换
 * DataBinder会把转换类型的操作委派给ConversionService和PropertyEditorRegistry
 * 但是对于有些数据格式，无法转换到目标类型，这时需要对DataBinder进行扩展
 * 有以下几种扩展方式:
 *  1.直接从工厂获取DataBinder对象，这种DataBinder绑定时无额外扩展转换能力
 *  2.用@InitBinder，给DataBinder添加扩展(PropertyEditorRegistry PropertyEditor)
 *  3.用ConversionService转换(ConversionService Formatter)
 *  4.同时加了@InitBinder和ConversionService
 *  5.使用默认ConversionService转换
 */
public class _2_DataBinder进行扩展 {

    public static void main(String[] args) throws Exception {
        /**
         * 准备请求数据(数据源)，绑定到Person对象p的属性上
         */
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("birthday", "1919|12|12");
        request.setParameter("address.name", "长安");
        Person p = new Person();
        /**
         * 上面的请求中，1919|12|12 无法转为Date类型，无论是ConversionService还是PropertyEditorRegistry，都没有这种转换功能，所以需要对类型转换进行扩展
         *  下面演示了几种方法来实现扩展
         */
        //1.直接从工厂获取原始WebDataBinder，无扩展的转换功能,factory.createBinder参数:请求对象，被绑定的对象，对象名称(随意)
//        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
//        WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), p, "person");

        /**
         * 2.用@InitBinder来扩展转换功能
         *      将控制器对象里的@InitBinder方法封装成InvocableHandlerMethod，赋给ServletRequestDataBinderFactory的第一个参数
         *      将来ServletRequestDataBinderFactory创建DataBinder，就会去依次回调@InitBinder方法，并将DataBinder对象作为参数
         *      所以，在@InitBinder方法里，我们可以给DataBinder添加扩展行为
         *  这种方法本质时使用PropertyEditorRegistry那套转换接口
         */
        InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("abc", WebDataBinder.class));
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), null);
        WebDataBinder binder1 = factory.createBinder(new ServletWebRequest(request), p, "person");

        /**
         * 3.用ConversionService转换
         *      FormattingConversionService是ConversionService实现类
         *      创建FormattingConversionService对象，添加一个Formatter类，并将FormattingConversionService封装成初始化器，赋值给ServletRequestDataBinderFactory第二个参赛
         */
        FormattingConversionService conversionService = new FormattingConversionService();
        conversionService.addFormatter(new MyDateFormatter("使用ConversionService给DataBinder添加扩展"));
        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(conversionService);
        factory = new ServletRequestDataBinderFactory(null, initializer);
        WebDataBinder binder2 = factory.createBinder(new ServletWebRequest(request), p, "person");

        /**
         * 4.同时给ServletRequestDataBinderFactory设置第一个参数和第二个参赛
         * 优先使用@InitBinder注册的转换器
         * 不明白的看看上一节的高层接口转换逻辑
         */
        factory = new ServletRequestDataBinderFactory(List.of(method), initializer);
        WebDataBinder binder3 = factory.createBinder(new ServletWebRequest(request), p, "person");

        /**
         * 5.使用默认ConversionService转换,配合注解@DateTimeFormat使用
         *      其实ConversionService内置了一个转换器，来进行日期格式的转换，使用@DateTimeFormat注解来指定使用这个转换器
         *  ApplicationConversionService也是ConversionService实现类，boot中存在的功能更丰富的ConversionService
         */
        ApplicationConversionService defaultConversionService = new ApplicationConversionService();
        ConfigurableWebBindingInitializer initializer1 = new ConfigurableWebBindingInitializer();
        initializer1.setConversionService(defaultConversionService);
        factory = new ServletRequestDataBinderFactory(null, initializer1);
        WebDataBinder binder4 = factory.createBinder(new ServletWebRequest(request), p, "person");

        binder4.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(p);
    }

    @Data
    static class Person {
        //使用这个注解，可以使用ConversionService内置转换器
        @DateTimeFormat(pattern = "yyyy|MM|dd")
        private Date birthday;

        private Address address;

        @Data
        static class Address {
            private String name;
        }
    }

    static class MyController {
        //自定义类型转换器，看起来是基于Formatter接口进行转换的，实际里面使用了适配器把Formatter转换成了PropertyEditorRegistry
        @InitBinder
        public void abc(WebDataBinder dataBinder) {
            //扩展dataBinder的功能，给dataBinder添加自定义转换器
            dataBinder.addCustomFormatter(new MyDateFormatter("使用@InitBinder给DataBinder添加扩展"));

        }
    }

    static class MyDateFormatter implements Formatter<Date> {

        public MyDateFormatter(String msg) {
            System.out.println(msg);
        }
        @Override
        public String print(Date object, Locale locale) {
            return DateUtil.format(object, "yyyy|MM|dd");
        }
        @Override
        public Date parse(String text, Locale locale) throws ParseException {
            return DateUtil.parse(text, "yyyy|MM|dd");
        }
    }
}
